React チュートリアル
三目並べを通じて基礎を学ぶReactのチュートリアル。
セットアップ
code:sh
npx create-react-app my-app
cd my-app
rm -f src/*
以下のファイルを追加
src/index.css
src/index.js
src/index.jsへ以下の3行を追加
code:src/index.js
import React from 'react';
import ReactDOM from 'react-dom/client';
import './index.css';
npm startを実行すると、ブラウザで http://localhost:3000/が開かれる。
以下のようなマスが表示される。
https://gyazo.com/3101e6c92de894ab1f316b4696f0b8aa
BoardからSquareへデータを渡す
code:diff
diff --git a/src/index.js b/src/index.js
index 18f0152..d9b24ba 100644
--- a/src/index.js
+++ b/src/index.js
@@ -6,7 +6,7 @@ class Square extends React.Component {
render() {
return (
<button className="square">
- {/* TODO */}
+ {this.props.value}
</button>
);
}
@@ -14,7 +14,7 @@ class Square extends React.Component {
class Board extends React.Component {
renderSquare(i) {
- return <Square />;
+ return <Square value={i} />;
}
render() {
こうなる。
https://gyazo.com/049235ac5d37347473618b7291e38412
インタラクティブなコンポーネントを作る
クリックイベントを拾えるようにする。
code:diff
diff --git a/src/index.js b/src/index.js
index d9b24ba..e1abcd1 100644
--- a/src/index.js
+++ b/src/index.js
@@ -5,7 +5,7 @@ import './index.css';
class Square extends React.Component {
render() {
return (
- <button className="square">
+ <button className="square" onClick={()=>{console.log('click')}}>
{this.props.value}
</button>
);
state.valueを追加し、Squareクリック時にstate.valueにXをセットするようにする。
code:diff
diff --git a/src/index.js b/src/index.js
index e1abcd1..bd777ca 100644
--- a/src/index.js
+++ b/src/index.js
@@ -3,10 +3,20 @@ import ReactDOM from 'react-dom/client';
import './index.css';
class Square extends React.Component {
+ constructor(props) {
+ super(props);
+ this.state = {
+ value: null,
+ };
+ }
+
render() {
return (
- <button className="square" onClick={()=>{console.log('click')}}>
- {this.props.value}
+ <button
+ className="square"
+ onClick={() => { this.setState({ value: ('X') }) }}
+ >
+ {this.state.value}
</button>
);
}
Developer Tool
React Developer Toolsをインストール。
コンポーネントツリーや、各コンポーネントのpropsやstateを確認できる。
https://gyazo.com/0d9ffbc0492049ad42f58d274facaad2
Stateのリフトアップ
Squareの情報をBoardへ持たせるようにする。また、Squareをクリックした時のハンドラをBoardで定義し、Squareはprops経由でそれを呼び出すようにする。
code:diff
diff --git a/src/index.js b/src/index.js
index bd777ca..b70fc94 100644
--- a/src/index.js
+++ b/src/index.js
@@ -14,17 +14,35 @@ class Square extends React.Component {
return (
<button
className="square"
- onClick={() => { this.setState({ value: ('X') }) }}
+ onClick={() => this.props.onClick()}
- {this.state.value}
+ {this.props.value}
</button>
);
}
}
class Board extends React.Component {
+ constructor(props) {
+ super(props);
+ this.state = {
+ squares: Array(9).fill(null),
+ };
+ }
+
+ handleClick(i) {
+ const squares = this.state.squares.slice();
+ this.setState({squares: squares});
+ }
+
renderSquare(i) {
- return <Square value={i} />;
+ return (
+ <Square
+ value={this.state.squaresi} + onClick={() => {this.handleClick(i)}}
+ />
+ );
}
render() {
関数コンポーネント
Squareを関数コンポーネントに書き換え。
code:diff
diff --git a/src/index.js b/src/index.js
index b70fc94..2a4a796 100644
--- a/src/index.js
+++ b/src/index.js
@@ -2,24 +2,15 @@ import React from 'react';
import ReactDOM from 'react-dom/client';
import './index.css';
-class Square extends React.Component {
- constructor(props) {
- super(props);
- this.state = {
- value: null,
- };
- }
-
- render() {
- return (
- <button
- className="square"
- onClick={() => this.props.onClick()}
- >
- {this.props.value}
- </button>
- );
- }
+function Square(props) {
+ return (
+ <button
+ className="square"
+ onClick={() => props.onClick()}
+ >
+ {props.value}
+ </button>
+ );
}
class Board extends React.Component {
手番の処理
XとOを交互に出すようにする。
code:diff
diff --git a/src/index.js b/src/index.js
index 2a4a796..d9c8137 100644
--- a/src/index.js
+++ b/src/index.js
@@ -18,13 +18,17 @@ class Board extends React.Component {
super(props);
this.state = {
squares: Array(9).fill(null),
+ xIsNext: true,
};
}
handleClick(i) {
const squares = this.state.squares.slice();
- this.setState({squares: squares});
+ squaresi = this.state.xIsNext ? 'X' : 'O'; + this.setState({
+ squares: squares,
+ xIsNext: !this.state.xIsNext,
+ });
}
renderSquare(i) {
次のプレイヤーが誰かを表示する
code:diff
diff --git a/src/index.js b/src/index.js
index d9c8137..1f6d1ba 100644
--- a/src/index.js
+++ b/src/index.js
@@ -41,7 +41,7 @@ class Board extends React.Component {
}
render() {
- const status = 'Next player: X';
+ const status = Next player: ${this.state.xIsNext ? 'X' : 'O'};
return (
<div>
ゲームの勝者の判定
Boardのrenderで勝敗判定を呼び出し、勝敗がついたら勝者を表示するようにする。
code:diff
diff --git a/src/index.js b/src/index.js
index d9c8137..a7dc8d0 100644
--- a/src/index.js
+++ b/src/index.js
@@ -41,7 +41,13 @@ class Board extends React.Component {
}
render() {
- const status = 'Next player: X';
+ const winner = calculateWinner(this.state.squares);
+ let status;
+ if (winner) {
+ status = Winner: ${winner};
+ } else {
+ status = Next player: ${this.state.xIsNext ? 'X' : 'O'};
+ }
return (
<div>
@@ -86,3 +92,23 @@ class Game extends React.Component {
const root = ReactDOM.createRoot(document.getElementById("root"));
root.render(<Game />);
+
+function calculateWinner(squares) {
+ const lines = [
+ ];
+ for (let i = 0; i < lines.length; i++) {
+ if (squaresa && squaresa === squaresb && squaresa === squaresc) { + }
+ }
+ return null;
+}
ステートをBoardからGameへ移動
code:diff
diff --git a/src/index.js b/src/index.js
index f3a8a84..50fdd6a 100644
--- a/src/index.js
+++ b/src/index.js
@@ -14,44 +14,18 @@ function Square(props) {
}
class Board extends React.Component {
- constructor(props) {
- super(props);
- this.state = {
- squares: Array(9).fill(null),
- xIsNext: true,
- };
- }
-
- handleClick(i) {
- const squares = this.state.squares.slice();
- squaresi = this.state.xIsNext ? 'X' : 'O'; - this.setState({
- squares: squares,
- xIsNext: !this.state.xIsNext,
- });
- }
-
renderSquare(i) {
return (
<Square
- value={this.state.squaresi} - onClick={() => {this.handleClick(i)}}
+ value={this.props.squaresi} + onClick={() => {this.props.onClick(i)}}
/>
);
}
render() {
- const winner = calculateWinner(this.state.squares);
- let status;
- if (winner) {
- status = Winner: ${winner};
- } else {
- status = Next player: ${this.state.xIsNext ? 'X' : 'O'};
- }
-
return (
<div>
- <div className="status">{status}</div>
<div className="board-row">
{this.renderSquare(0)}
{this.renderSquare(1)}
@@ -73,14 +47,55 @@ class Board extends React.Component {
}
class Game extends React.Component {
+ constructor(props) {
+ super(props);
+ this.state = {
+ squares: Array(9).fill(null),
+ history: [{
+ squares: Array(9).fill(null),
+ }],
+ xIsNext: true,
+ }
+ }
+
+ handleClick(i) {
+ const history = this.state.history;
+ const squares = current.squares.slice();
+ if (calculateWinner(squares) || squaresi) { + return;
+ }
+
+ squaresi = this.state.xIsNext ? 'X' : 'O'; + this.setState({
+ history: history.concat([{
+ squares: squares,
+ }]),
+ xIsNext: !this.state.xIsNext,
+ });
+ }
+
render() {
+ const history = this.state.history;
+ const winner = calculateWinner(current.squares);
+ let status;
+ if (winner) {
+ status = Winner: ${winner};
+ } else {
+ status = Next player: ${this.state.xIsNext ? 'X' : 'O'};
+ }
+
return (
<div className="game">
<div className="game-board">
- <Board />
+ <Board
+ squares={current.squares}
+ onClick={(i) => this.handleClick(i)}
+ />
</div>
<div className="game-info">
- <div>{/* status */}</div>
+ <div>{status}</div>
<ol>{/* TODO */}</ol>
</div>
</div>
過去の着手の表示
code:diff
81a82,95
const moves = history.map((step, move) => {
const desc = move ?
'Go to game start';
return (
<li key={move}>
<button
onClick={() => this.jumpTo(move)}
>
{desc}
</button>
</li>
);
});
99c113
< <ol>{/* TODO */}</ol>
---
<ol>{moves}</ol>
過去の履歴へとべるようにする
code:diff
diff --git a/src/index.js b/src/index.js
index f819e5d..e140ebb 100644
--- a/src/index.js
+++ b/src/index.js
@@ -54,13 +54,14 @@ class Game extends React.Component {
history: [{
squares: Array(9).fill(null),
}],
+ stepNumber: 0,
xIsNext: true,
}
}
handleClick(i) {
- const history = this.state.history;
+ const history = this.state.history.slice(0, this.state.stepNumber + 1);
const squares = current.squares.slice();
if (calculateWinner(squares) || squaresi) { return;
@@ -71,13 +72,21 @@ class Game extends React.Component {
history: history.concat([{
squares: squares,
}]),
+ stepNumber: history.length,
xIsNext: !this.state.xIsNext,
});
}
+ jumpTo(step) {
+ this.setState({
+ stepNumber: step,
+ xIsNext: (step % 2) === 0,
+ });
+ }
+
render() {
const history = this.state.history;
const winner = calculateWinner(current.squares);
const moves = history.map((step, move) => {
const desc = move ?
これで完成
https://gyazo.com/918c24dcef51889bf26a6e12df6ee9e9
より掘り下げた説明はこちら
コンポーネントの作成方法について詳細に学ぶにはこちら
おまけ
ビルドしたファイルで実行してみる。
code:sh
cd ~/src/my-app
# ビルド
npm run build
# webrickが必要なのでインストール(オプション)
gem install webrick
# Webサーバを起動
ruby -rwebrick -e 'WEBrick::HTTPServer.new(:DocumentRoot => "./build", :Port => 8000).start'
ブラウザでhttp://localhost:8000/へアクセスし、三目並べが表示されればOK
https://gyazo.com/793f5ce3e833e9cb0c1b535ac140aa69